package com.bensonbasement.snorflux.server.world;

import java.util.HashSet;
import java.util.Set;

import rlforj.los.IFovAlgorithm;
import rlforj.los.ILosBoard;
import rlforj.los.PrecisePermissive;

import com.bensonbasement.snorflux.server.world.prototypes.ActorPrototype;
import com.bensonbasement.snorflux.server.world.prototypes.BigEntityPrototype;

public class Actor extends BigEntity {
	
	private Set<Space> visibleSpaces;
	private Brain brain = null;
	
	private int ap;
	
	public Actor(World world, ActorPrototype prototype) {
		super(world, prototype);
		this.visibleSpaces = new HashSet<Space>();
	}
	
	@Override
	protected ActorPrototype getPrototype() {
		return (ActorPrototype)super.getPrototype();
	}
	
	public void setBrain(Brain newBrain) {
		Brain oldBrain = this.brain;
		if (oldBrain != newBrain) {
			if (oldBrain != null) {
				oldBrain.removeControlledActor(this);
			}
			if (newBrain != null) {
				newBrain.addControlledActor(this);
			}
			this.brain = newBrain;
		}
	}
	
	public void updateVisibility() {
		if (this.isAnimated()) {
			this.visibleSpaces = generateVisibility();
			if (this.brain != null) {
				this.brain.actorVisibilityUpdated();
			}
		}
	}
	
	private Set<Space> generateVisibility() {
		final Set<Space> newVisibleSpaces = new HashSet<Space>();
		Space space = this.getSpace();
		if (space != null) {
			final Level level = this.getLevel();
			ILosBoard board = new ILosBoard() {

				@Override
				public boolean contains(int x, int y) {
					Space space = level.getSpace(x,y);
					return (space != null); // True if the space exists.
				}

				@Override
				public boolean isObstacle(int x, int y) {
					Space space = level.getSpace(x,y);
					BigEntity bigEntity = space.getBigEntity();
					return bigEntity != null && bigEntity.isVisionBlocker();
				}

				@Override
				public void visit(int x, int y) {
					Space space = level.getSpace(x,y);
					newVisibleSpaces.add(space);
				}
			};
			IFovAlgorithm alg = new PrecisePermissive();
			// TODO: Update the distance parameter below to something more appropriate.
			alg.visitFieldOfView(board, space.getX(), space.getY(), 20);
		}
		return newVisibleSpaces;
	}

	public Set<Space> getVisibleSpaces() {
		return visibleSpaces;
	}
	
	@Override
	public void setLocation(IBigEntityHolder newLocation) {
		IBigEntityHolder oldLocation = this.getLocation();
		super.setLocation(newLocation);
		if (oldLocation != newLocation) {
			Level oldLevel;
			Level newLevel;
			if (oldLocation == null) {
				oldLevel = null;
			}
			else {
				oldLevel = oldLocation.getLevel();
			}
			if (newLocation == null) {
				newLevel = null;
			}
			else {
				newLevel = newLocation.getLevel();
			}
			if (newLevel != oldLevel) {
				if (oldLevel != null) {
					oldLevel.removeActor(this);
				}
				if (newLevel != null) {
					newLevel.addActor(this);
				}
			}
			this.updateVisibility();
		}
	}
	
	public BrainTeam getBrainTeam() {
		// TODO: Remove this method?
		if (this.brain == null) {
			return null;
		}
		else {
			return this.brain.getBrainTeam();
		}
	}
	
	public Brain getBrain() {
		return this.brain;
	}
	
	public void startTurn() {
		setAP(getAP()+getAPPerTurn());
	}
	
	@Override
	protected void animateHelper() {
		super.animateHelper();
		this.updateVisibility();
	}

	public int getAP() {
		return ap;
	}
	
	public void setAP(int ap) {
		if (ap < 0) {
			throw new RuntimeException("Cannot have negative AP!");
		}
		else if (ap > getMaxAP()) {
			this.ap = getMaxAP();
		}
		else {
			this.ap = ap;
		}
	}
	
	public void spendAP(int ap) {
		setAP(getAP() - ap);
	}
	
	private int getMaxAP() {
		return getPrototype().findMaxAP();
	}
	
	private int getAPPerTurn() {
		return getPrototype().findAPPerTurn();
	}
}
